﻿using SharpDepend.Classes;
using SharpDepend.Datatypes;
using SharpDepend.WPF.Controls;
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
using System.Collections.Generic;

namespace SharpDepend.WindowsDesktop.Renderer
{
    /// <summary>
    /// Interaction logic for SoftwareRenderer.
    /// </summary>
    public class SoftwareRenderer : Image, IGraphicsDevice
    {
        private WriteableBitmap wBitmap;
        private BuiltInGraphicsHost mBuiltInGraphicsHost;
        private int mCreatedThread;

        public System.Collections.ObjectModel.ReadOnlyCollection<Texture> Textures
        {
            get { return mBuiltInGraphicsHost.Textures; }
        }

        /// <summary>
        /// Always ready.
        /// </summary>
        public bool Ready
        {
            get;
            private set;
        }

        public event Action ResolutionChanged;
        public event Action OnManualRender;

        /// <summary>
        /// Create instance of software renderer.
        /// </summary>
        public SoftwareRenderer()
        {
            mCreatedThread = System.Threading.Thread.CurrentThread.ManagedThreadId;

            mBuiltInGraphicsHost = new BuiltInGraphicsHost(4, 4);
            SizeChanged += SoftwareRenderer_SizeChanged;
            Loaded += SoftwareRenderer_Loaded;

            //Stretch = System.Windows.Media.Stretch.Fill;
            RenderOptions.SetBitmapScalingMode(this, BitmapScalingMode.HighQuality);

            Stretch = Stretch.Fill;
        }

        /// <summary>
        /// Get the graphics display if available.
        /// </summary>
        public IGraphicsDisplay GraphicsDisplay { get; private set; }

        /// <summary>
        /// Calls wqhen this instance has been created.
        /// </summary>
        /// <param name="graphicsDisplay"></param>
        public void Initialize(IGraphicsDisplay graphicsDisplay)
        {
            GraphicsDisplay = graphicsDisplay;
        }

        void SoftwareRenderer_Loaded(object sender, RoutedEventArgs e)
        {
            SetViewportSize(DesiredSize.Width, DesiredSize.Height);
        }

        public void SetViewportSize(double width, double height)
        {
            Ready = false;
            width = Math.Max(4, (int)width);
            height = Math.Max(4, (int)height);

            mBuiltInGraphicsHost.SetViewportSize((int)width, (int)height);
            wBitmap = new WriteableBitmap((int)width, (int)height, 64, 64, PixelFormats.Bgra32, null);
            Source = wBitmap;

            var call = ResolutionChanged;
            if (call != null)
            {
                call();
            }
            Ready = true;
        }

        void SoftwareRenderer_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            lock (GraphicsDisplay.Locker)
            {
                SetViewportSize((int)e.NewSize.Width, (int)e.NewSize.Height);
            }
        }

        public object Locker
        {
            get { return mBuiltInGraphicsHost.Locker; }
        }

        public bool ManualRender
        {
            get { return true; }
        }

        public int RenderWidth
        {
            get { return mBuiltInGraphicsHost.RenderWidth; }
        }

        public int RenderHeight
        {
            get { return mBuiltInGraphicsHost.RenderHeight; }
        }

        public bool Enable
        {
            get
            {
                return mBuiltInGraphicsHost.Enable;
            }
            set
            {
                mBuiltInGraphicsHost.Enable = value;
            }
        }

        public bool RenderTextures
        {
            get
            {
                return mBuiltInGraphicsHost.RenderTextures;
            }
            set
            {
                mBuiltInGraphicsHost.RenderTextures = value;
            }
        }

        public bool ManualRendering
        {
            get
            {
                return false;
            }
        }

        public void RegisterTexture(Texture texture)
        {
            mBuiltInGraphicsHost.RegisterTexture(texture);
        }

        public void UnregisterTexture(Texture texture)
        {
            mBuiltInGraphicsHost.UnregisterTexture(texture);
        }

        public void UpdateTexture(Texture texture)
        {
            mBuiltInGraphicsHost.UpdateTexture(texture);
        }

        public void BeginRender()
        {
            mBuiltInGraphicsHost.BeginRender();
        }

        public void SetClip(Vector4 plane)
        {
            mBuiltInGraphicsHost.SetClip(plane);
        }

        public void ResetClip()
        {
            mBuiltInGraphicsHost.ResetClip();
        }

        public void Draw(ref Vector4 rectangle, SharpDepend.Datatypes.Color color)
        {
            mBuiltInGraphicsHost.Draw(ref rectangle, color);
        }

        public void Draw(ref Vector4 rectangle, SharpDepend.Datatypes.Color color, Texture texture, ref Vector4 destinationTextureUV)
        {
            mBuiltInGraphicsHost.Draw(ref rectangle, color, texture, ref destinationTextureUV);
        }

        public void DrawLine(Vector2 start, Vector2 end, SharpDepend.Datatypes.Color color)
        {
            mBuiltInGraphicsHost.DrawLine(start, end, color);
        }

        public void DrawCircle(Vector2 position, double radius, SharpDepend.Datatypes.Color color)
        {
            mBuiltInGraphicsHost.DrawCircle(position, radius, color);
        }

        public void EndRender()
        {
            // If the renderer is being rendering before the renderer element has initialized tha size.
            // It will be null. So null check it before.
            var bitmap = wBitmap;
            if (bitmap != null)
            {
                mBuiltInGraphicsHost.EndRender();

                int renderWidth = (int)RenderWidth;
                int renderHeight = (int)RenderHeight;
                int[] backBuffer = mBuiltInGraphicsHost.BackBuffer;
                
                // TODO: Preaty ugly?
                // Alternative: http://stackoverflow.com/questions/9868929/how-to-edit-a-writablebitmap-backbuffer-in-non-ui-thread
                try
                {
                    if (System.Threading.Thread.CurrentThread.ManagedThreadId == mCreatedThread)
                    {
                        Int32Rect rect = new Int32Rect(0, 0, renderWidth, renderHeight);
                        bitmap.Lock();
                        bitmap.WritePixels(rect, backBuffer, renderWidth * 4, 0);
                        bitmap.AddDirtyRect(rect);
                        bitmap.Unlock();
                    }
                    else
                    {
                        // Duble buffering.
                        var pixels = backBuffer;
                        if (!mInvalidating && pixels != null)
                        {
                            renderWidthCopied = RenderWidth;
                            renderHeightCopied = RenderHeight;
                            mInvalidating = true;
                            if (pixels.Length != pixelsCopied.Length)
                            {
                                pixelsCopied = new int[pixels.Length];
                            }
                            Array.Copy(pixels, pixelsCopied, pixelsCopied.Length);

                            Dispatcher.BeginInvoke((Action)delegate
                            {
                                Int32Rect rect = new Int32Rect(0, 0, renderWidthCopied, renderHeightCopied);
                                bitmap.Lock();
                                bitmap.WritePixels(rect, pixelsCopied, renderWidthCopied * 4, 0);
                                bitmap.AddDirtyRect(rect);
                                bitmap.Unlock();

                                mInvalidating = false;
                            }, null);
                        }

                    }
                }
                catch (Exception)
                {

                }
            }
        }

        private int[] pixelsCopied = new int[0];
        private int renderWidthCopied;
        private int renderHeightCopied;
        private bool mInvalidating;

        public void DisposeDevice()
        {
            lock (GraphicsDisplay.Locker)
            {
                Ready = false;
                SizeChanged -= SoftwareRenderer_SizeChanged;
                wBitmap = null;
            }
        }
        
        public void DrawTriangles(FastStructList<Vector2> vertices, FastStructList<ushort> indices, FastStructList<Datatypes.Color> colors)
        {
            throw new NotImplementedException();
        }
    }
}
